/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ocr;

import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;

/**
 *
 * @author jenkin90
 */
public class Ocr {

    private BufferedImage model = null;
    public static final Integer BLACK = -16777216;
    public static final Integer WHITE = -1;
    private Comparator<LabelsData> c = new comparadorPosicion();
    List<RecognizedLetter> modelos;
    private static Integer numeroLetras = 0;
    private static Integer aciertos = 0;
    private static Integer fallos = 0;

    public Ocr() {
        try {
            this.model = ImageIO.read(new File("d:/modelo5.png"));
        } catch (IOException ex) {
            Logger.getLogger(Ocr.class.getName()).log(Level.SEVERE, null, ex);
        }

        this.modelos = this.prepareImage(model);
        Integer i = 0;
        for (RecognizedLetter r : this.modelos) {
            r.setLetter(i.toString());
            i++;
        }
//        for (RecognizedLetter r : this.modelos) {
//            try {
//                ImageIO.write(r.getImage(), "png", new File("d:/test/" + r.getLetra() + ".png"));
//            } catch (IOException ex) {
//                Logger.getLogger(Ocr.class.getName()).log(Level.SEVERE, null, ex);
//            }
//            // System.out.println(r);
//        }
    }

    private BufferedImage erode(BufferedImage img) {
        Integer width = img.getWidth();
        Integer height = img.getHeight();
        BufferedImage res = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY);
        BufferedImage structuralElement = this.getStructuralElement();


        for (int i = 1; i < img.getWidth() - 1; i++) {
            for (int j = 1; j < img.getHeight() - 1; j++) {
                if (img.getRGB(i, j) == Ocr.BLACK) {
                    boolean inStructuralElement = true;
                    for (int k = 0; k < 3; k++) {
                        for (int l = 0; l < 3; l++) {
                            Integer colorStructural = structuralElement.getRGB(k, l);
                            if (colorStructural.equals(Ocr.BLACK)) {
                                Integer colorImagen = img.getRGB(i + k - 1, j + l - 1);
                                if (!colorImagen.equals(colorStructural)) {
                                    inStructuralElement = false;
                                }

                            }
                        }
                    }
                    if (inStructuralElement) {
                        res.setRGB(i, j, img.getRGB(i, j));
                    } else {
                        res.setRGB(i, j, Ocr.WHITE);
                    }
                } else {
                    res.setRGB(i, j, img.getRGB(i, j));
                }

            }
        }
        return res;
    }

    private BufferedImage dilate(BufferedImage img) {
        Integer width = img.getWidth();
        Integer height = img.getHeight();
        BufferedImage res = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY);
        BufferedImage structuralElement = this.getStructuralElement();

        for (int i = 1; i < img.getWidth() - 1; i++) {
            for (int j = 1; j < img.getHeight() - 1; j++) {
                if (img.getRGB(i, j) == Ocr.BLACK) {
                    for (int k = 0; k < 3; k++) {
                        for (int l = 0; l < 3; l++) {
                            Integer colorStructural = structuralElement.getRGB(k, l);
                            if (colorStructural.equals(Ocr.BLACK)) {
                                res.setRGB(i + k - 1, j + l - 1, Ocr.BLACK);
                            }
                        }
                    }

                } else {
                    res.setRGB(i, j, img.getRGB(i, j));
                }

            }
        }
        return res;
    }

    public BufferedImage getStructuralElement() {
        BufferedImage res = new BufferedImage(3, 3, BufferedImage.TYPE_BYTE_BINARY);

        return res;
    }

    private List<RecognizedLetter> prepareImage(BufferedImage img) {
        img = this.dilate(img);
        img = this.dilate(img);
        img = this.dilate(img);
        //img = this.erode(img);

        img = this.deleteBorder(img);
        List<BufferedImage> imageList2 = subImage(img);
        List<RecognizedLetter> modelos = new ArrayList<RecognizedLetter>();

        List<BufferedImage> imageList = new ArrayList<BufferedImage>();

        for (BufferedImage ske : imageList2) {
//           Hilditch esqueletizador=new Hilditch(ske);
//           BufferedImage esqueleto=esqueletizador.thin();
            BufferedImage esqueleto = skeleton(ske);
//            try {
//                ImageIO.write(esqueleto, "png", new File("d:/test/" + Math.random() + ".png"));
//            } catch (IOException ex) {
//                Logger.getLogger(Ocr.class.getName()).log(Level.SEVERE, null, ex);
//            }
            imageList.add(esqueleto);
        }

        for (BufferedImage processedImage : imageList) {
            RecognizedLetter aux = new RecognizedLetter(processedImage);
            if (aux.getArea() > 200) {
                modelos.add(aux);
            }
        }
        return modelos;
    }

    private List<BufferedImage> subImage(BufferedImage img) {
        List<BufferedImage> res = new ArrayList<BufferedImage>();
        List<List<Integer>> labels = null;


        Map<Integer, LabelsData> usedLabels = new HashMap<Integer, LabelsData>();

        labels = labelImage(img, BLACK, usedLabels);

        SortedSet<LabelsData> orderedLabels = new TreeSet<LabelsData>(c);
        for (Integer i : usedLabels.keySet()) {
            orderedLabels.add(usedLabels.get(i));
        }

        for (LabelsData aux : orderedLabels) {

            Integer width = aux.getMaxX() - aux.getMinX() + 1;
            Integer heigth = aux.getMaxY() - aux.getMinY() + 1;
            BufferedImage imgLabel = img.getSubimage(aux.getMinX(), aux.getMinY(), width, heigth);
            res.add(imgLabel);
        }

        return res;
    }

    private static void updateLabels(Integer newLabel, Integer oldLabel, List<List<Integer>> labels, Map<Integer, LabelsData> usedLabels) {
        for (int i = 0; i < labels.size(); i++) {
            for (int j = 0; j < labels.get(i).size(); j++) {
                if (labels.get(i).get(j).equals(oldLabel)) {
                    labels.get(i).set(j, newLabel);
                }
            }
        }
        LabelsData auxOld = usedLabels.get(oldLabel);
        LabelsData auxNew = usedLabels.get(newLabel);
        if (auxOld.getMinX() < auxNew.getMinX()) {
            auxNew.setMinX(auxOld.getMinX());
        }
        if (auxOld.getMinY() < auxNew.getMinY()) {
            auxNew.setMinY(auxOld.getMinY());
        }
        if (auxOld.getMaxX() > auxNew.getMaxX()) {
            auxNew.setMaxX(auxOld.getMaxX());
        }
        if (auxOld.getMaxY() > auxNew.getMaxY()) {
            auxNew.setMaxY(auxOld.getMaxY());
        }
        usedLabels.put(newLabel, auxNew);
        usedLabels.remove(oldLabel);
    }

    private BufferedImage deleteBorder(BufferedImage img) {
        for (int i = 0; i < img.getWidth(); i++) {
            img.setRGB(i, 0, Ocr.WHITE);
            img.setRGB(i, img.getHeight() - 1, Ocr.WHITE);
        }
        for (int i = 0; i < img.getHeight(); i++) {
            img.setRGB(0, i, Ocr.WHITE);
            img.setRGB(img.getWidth() - 1, i, Ocr.WHITE);
        }
        return img;
    }

    private static void modifyLabelData(int i, int j, Integer label, Map<Integer, LabelsData> usedLabels) {
        LabelsData aux;
        if (usedLabels.containsKey(label)) {
            aux = usedLabels.get(label);
            if (i < aux.getMinX()) {
                aux.setMinX(i);
            }
            if (i > aux.getMaxX()) {
                aux.setMaxX(i);
            }
            if (j < aux.getMinY()) {
                aux.setMinY(j);
            }
            if (j > aux.getMaxY()) {
                aux.setMaxY(j);
            }
        } else {
            aux = new LabelsData();
            aux.setMinX(i);
            aux.setMaxX(i);
            aux.setMinY(j);
            aux.setMaxY(j);
        }
//        System.out.println(aux);
        usedLabels.put(label, aux);
    }

    public static List<List<Integer>> labelImage(BufferedImage img, Integer foreColor, Map<Integer, LabelsData> usedLabels) {
        List<List<Integer>> labels = new ArrayList<List<Integer>>();
        for (int i = 0; i <= img.getWidth(); i++) {
            List<Integer> temp = new ArrayList<Integer>();
            for (int j = 0; j <= img.getHeight(); j++) {
                temp.add(0);
            }
            labels.add(temp);
        }
        Integer label = 1;

        for (int j = 1; j < img.getHeight(); j++) {
            for (int i = 1; i < img.getWidth(); i++) {

                if (img.getRGB(i, j) == foreColor) {   //Si hay algo en el punto actual
                    if (img.getRGB(i - 1, j - 1) == img.getRGB(i, j)) {   //Si es igual a la diagonal
                        labels.get(i).set(j, labels.get(i - 1).get(j - 1));
                        modifyLabelData(i, j, labels.get(i - 1).get(j - 1), usedLabels);

                    } else {  // No es igual a la diagonal
                        if (img.getRGB(i - 1, j) == img.getRGB(i, j)) {    //Si es igual al anterior
                            labels.get(i).set(j, labels.get(i - 1).get(j));
                            modifyLabelData(i, j, labels.get(i - 1).get(j), usedLabels);
                            if (img.getRGB(i, j) == img.getRGB(i, j - 1)) { //Si es igual al de arriba

                                if (!labels.get(i - 1).get(j).equals(labels.get(i).get(j - 1))) { //Si la etiqueta del anterior igual a la del de arriba
                                    updateLabels(labels.get(i).get(j - 1), labels.get(i - 1).get(j), labels, usedLabels);
                                }
                            }


                        } else {  //No es igual a la anterior
                            if (img.getRGB(i, j) == img.getRGB(i, j - 1)) { //Si es igual al de arriba
                                labels.get(i).set(j, labels.get(i).get(j - 1));
                                modifyLabelData(i, j, labels.get(i).get(j - 1), usedLabels);
                            } else {   //Si no es igual al de arriba
                                labels.get(i).set(j, label);
                                modifyLabelData(i, j, label, usedLabels);
                                label++;
                            }
                        }

                    }
                }
            }

        }
        return labels;
    }

    public String doOcr(BufferedImage img) {

        List<RecognizedLetter> letras = prepareImage(img);

//        System.out.println("Resultado: 1327245");
//        Integer i = 0;
//        for (RecognizedLetter r : letras) {
//            try {
//                ImageIO.write(r.getImage(), "png", new File("D:/test/a" + i + ".png"));
//            } catch (IOException ex) {
//                Logger.getLogger(Ocr.class.getName()).log(Level.SEVERE, null, ex);
//            }
//            //System.out.println(r);
//            i++;
//        }
        String cadena = "";


        for (RecognizedLetter r : letras) {
            Map<String, Integer> puntuaciones = new HashMap<String, Integer>();
            for (RecognizedLetter rec : this.modelos) {
                puntuaciones.put(rec.getLetra(), 0);
            }
            for (RecognizedLetter letraModelo : modelos) {

                if (r.getEuler().equals(letraModelo.getEuler())) {
                    if (r.getCentreOfMass().distance(letraModelo.getCentreOfMass()) <= 1) {
                        puntuaciones.put(letraModelo.getLetra(), puntuaciones.get(letraModelo.getLetra()) + 1);
                    }
//                System.out.println(letraModelo.getCentreOfMass() + " ("+letraModelo.getLetra()+")-" + r.getCentreOfMass()+": "+r.getCentreOfMass().distance(letraModelo.getCentreOfMass()));
                    long difMasas = Math.abs(Math.round(((r.getRelativeArea() - letraModelo.getRelativeArea()) / letraModelo.getRelativeArea()) * 100.0));
//                System.out.println(letraModelo.getRelativeArea() + " ("+letraModelo.getLetra()+")-" + r.getRelativeArea()+": "+difMasas);
                    if (difMasas <= 5) {
                        puntuaciones.put(letraModelo.getLetra(), puntuaciones.get(letraModelo.getLetra()) + 1);

                    }
                    double difMinInercia = (double) (r.getMinInertia() - letraModelo.getMinInertia()) / letraModelo.getMinInertia();
                    difMinInercia = Math.abs(Math.round(difMinInercia * 100.0));
//                System.out.println(letraModelo.getMinInertia() + " ("+letraModelo.getLetra()+")- " + r.getMinInertia()+": "+difMinInercia);
                    if (difMinInercia <= 5) {
                        puntuaciones.put(letraModelo.getLetra(), puntuaciones.get(letraModelo.getLetra()) + 1);

                    }
                    double difMaxInercia = (double) (r.getMaxInertia() - letraModelo.getMaxInertia()) / letraModelo.getMaxInertia();
                    difMaxInercia = Math.abs(Math.round(difMaxInercia * 100.0));
                    if (difMaxInercia <= 5) {
                        puntuaciones.put(letraModelo.getLetra(), puntuaciones.get(letraModelo.getLetra()) + 1);

                    }
//                    long difPhi1 = Math.abs(Math.round(((r.phi1() - letraModelo.phi1()) / letraModelo.phi1()) * 100.0));
//                    if (difPhi1 <= 5) {
//                        puntuaciones.put(letraModelo.getLetra(), puntuaciones.get(letraModelo.getLetra()) + 1);
//
//                    }
//                    long difPhi2 = Math.abs(Math.round(((r.phi2() - letraModelo.phi2()) / letraModelo.phi2()) * 100.0));
//                    if (difPhi2 <= 5) {
//                        puntuaciones.put(letraModelo.getLetra(), puntuaciones.get(letraModelo.getLetra()) + 1);
//
//                    }
//                    long difPhi3 = Math.abs(Math.round(((r.phi3() - letraModelo.phi3()) / letraModelo.phi3()) * 100.0));
//                    if (difPhi3 <= 3) {
//                        puntuaciones.put(letraModelo.getLetra(), puntuaciones.get(letraModelo.getLetra()) + 1);
//
//                    }
//                    long difPhi4 = Math.abs(Math.round(((r.phi4() - letraModelo.phi4()) / letraModelo.phi4()) * 100.0));
//                    if (difPhi4 <= 3) {
//                        puntuaciones.put(letraModelo.getLetra(), puntuaciones.get(letraModelo.getLetra()) + 1);
//
//                    }
//                    long difPhi5 = Math.abs(Math.round(((r.phi5() - letraModelo.phi5()) / letraModelo.phi5()) * 100.0));
//                    if (difPhi5 <= 3) {
//                        puntuaciones.put(letraModelo.getLetra(), puntuaciones.get(letraModelo.getLetra()) + 1);
//
//                    }
//                    long difPhi6 = Math.abs(Math.round(((r.phi6() - letraModelo.phi6()) / letraModelo.phi6()) * 100.0));
//                    if (difPhi6 <= 3) {
//                        puntuaciones.put(letraModelo.getLetra(), puntuaciones.get(letraModelo.getLetra()) + 1);
//
//                    }
//                    long difPhi7 = Math.abs(Math.round(((r.phi7() - letraModelo.phi7()) / letraModelo.phi7()) * 100.0));
//                    if (difPhi7 <= 3) {
//                        puntuaciones.put(letraModelo.getLetra(), puntuaciones.get(letraModelo.getLetra()) + 1);
//
//                    }
                    System.out.print(puntuaciones);
                }
            }
            Integer max = 0;
            String letra = "";


            for (String letraGanadora : puntuaciones.keySet()) {
                if (puntuaciones.get(letraGanadora) > max) {
                    max = puntuaciones.get(letraGanadora);
                    letra = letraGanadora;
                } else {
                    if (puntuaciones.get(letraGanadora).equals(max) && max > 0) {
                        letra = "X";
                    }
                }
            }
            Ocr.numeroLetras++;
            if (letra.equals("X")) {
                Ocr.fallos++;
            }
            cadena += letra;
        }
        //System.out.println(cadena);
        System.out.println("Letras: " + Ocr.numeroLetras + "  Fallos: " + Ocr.fallos);
        return cadena;
    }

    public Double comparacion(RecognizedLetter letraModelo, RecognizedLetter r) {
        Double resultado;

        if (letraModelo.getEuler().equals(r.getEuler())) {

            Double temp1 = Math.abs(letraModelo.phi1() - r.phi1()) / letraModelo.phi1();
            Double temp2 = Math.abs(letraModelo.phi2() - r.phi2()) / letraModelo.phi2();
            Double temp3 = Math.abs(letraModelo.phi3() - r.phi3()) / letraModelo.phi3();
            Double temp4 = Math.abs(letraModelo.phi4() - r.phi4()) / letraModelo.phi4();
            Double temp5 = Math.abs(letraModelo.phi5() - r.phi5()) / letraModelo.phi5();
            Double temp6 = Math.abs(letraModelo.phi6() - r.phi6()) / letraModelo.phi6();
            Double temp7 = Math.abs(letraModelo.phi7() - r.phi7()) / letraModelo.phi7();

            resultado = (temp1 + temp2 + temp3 + temp4 + temp5 + temp6 + temp7) / 7;
        } else {
            resultado = 1000000000.0;
        }

        return resultado;
    }

    private BufferedImage skeleton(BufferedImage img) {

        System.out.println("_-----------------_");

        BufferedImage res = new BufferedImage(img.getWidth() + 4, img.getHeight() + 4, BufferedImage.TYPE_BYTE_BINARY);
        BufferedImage ske = new BufferedImage(img.getWidth() + 4, img.getHeight() + 4, BufferedImage.TYPE_BYTE_BINARY);
        Boolean haCambiado = true;
        Set<Point> pixelsToRemove = new HashSet<Point>();

        for (int i = 0; i < img.getWidth(); i++) {
            for (int j = 0; j < img.getHeight(); j++) {

                ske.setRGB(i + 2, j + 2, img.getRGB(i, j));

            }
        }
        for (int i = 0; i < ske.getWidth(); i++) {
            ske.setRGB(i, 0, Ocr.WHITE);
            ske.setRGB(i, 1, Ocr.WHITE);
            ske.setRGB(i, ske.getHeight() - 1, Ocr.WHITE);
            ske.setRGB(i, ske.getHeight() - 2, Ocr.WHITE);
            res.setRGB(i, 0, Ocr.WHITE);
            res.setRGB(i, 1, Ocr.WHITE);
            res.setRGB(i, ske.getHeight() - 1, Ocr.WHITE);
            res.setRGB(i, ske.getHeight() - 2, Ocr.WHITE);
        }
        for (int i = 0; i < ske.getHeight(); i++) {
            ske.setRGB(0, i, Ocr.WHITE);
            ske.setRGB(1, i, Ocr.WHITE);
            ske.setRGB(ske.getWidth() - 1, i, Ocr.WHITE);
            ske.setRGB(ske.getWidth() - 2, i, Ocr.WHITE);
            res.setRGB(0, i, Ocr.WHITE);
            res.setRGB(1, i, Ocr.WHITE);
            res.setRGB(ske.getWidth() - 1, i, Ocr.WHITE);
            res.setRGB(ske.getWidth() - 2, i, Ocr.WHITE);
        }


//        try {
//            ImageIO.write(ske, "png", new File("d:/test/aaa.png"));
//        } catch (IOException ex) {
//            Logger.getLogger(Ocr.class.getName()).log(Level.SEVERE, null, ex);
//        }
        while (haCambiado) {
            haCambiado = false;

            for (int i = 2; i < ske.getWidth() - 2; i++) {
                for (int j = 2; j < ske.getHeight() - 2; j++) {
                    Boolean loQuitamos1 = true, loQuitamos2 = true, loQuitamos3 = true, loQuitamos4 = true;
                    if (ske.getRGB(i, j) == Ocr.BLACK) {
                        Integer a = a(ske, i, j);
                        Integer b = b(ske, i, j);
                        if (b <= 2 && b > 6) {

                            loQuitamos1 = false;
                        }
                        if (a == 1) {
                            loQuitamos2 = false;
                        }

                        Integer p2 = ske.getRGB(i, j - 1);
                        Integer p4 = ske.getRGB(i + 1, j);
                        Integer p6 = ske.getRGB(i, j - 1);
                        Integer p8 = ske.getRGB(i - 1, j);
                        if ((p2 == Ocr.BLACK && p4 == Ocr.BLACK && p8 == Ocr.BLACK) || a(ske, i, j - 1) == 1) {
                            // if (a(ske, i, j - 1) == 1) {
                            loQuitamos3 = false;
                        }
                        if ((p2 == Ocr.BLACK && p4 == Ocr.BLACK && p6 == Ocr.BLACK) || a(ske, i + 1, j) == 1) {
                            //if (a(ske, i + 1, j) == 1) {
                            loQuitamos4 = false;
                        }
                        if (loQuitamos1 && loQuitamos2 && loQuitamos3 && loQuitamos4) {
                            Point p = new Point(i, j);
                            pixelsToRemove.add(p);
                            haCambiado = true;
                        }
                    } else {
                        res.setRGB(i, j, Ocr.WHITE);
                    }
                }
            }
            if (haCambiado) {
                for (Point p : pixelsToRemove) {
                    ske.setRGB((int) p.getX(), (int) p.getY(), Ocr.WHITE);
                }
            }
        }

//        try {
//            ImageIO.write(res.getSubimage(2, 2, img.getWidth(), img.getHeight()), "png", new File("d:/test/bbb.png"));
//        } catch (IOException ex) {
//            Logger.getLogger(Ocr.class.getName()).log(Level.SEVERE, null, ex);
//        }

        return ske.getSubimage(2, 2, img.getWidth(), img.getHeight());
    }

    private Integer b(BufferedImage img, Integer x, Integer y) {
        //Devuelve el número de negros que rodean a un punto
        Integer result = 0;
        Integer actual;


        actual = img.getRGB(x, y - 1);

        if (actual.equals(Ocr.BLACK)) {
            result++;
        }

        actual = img.getRGB(x + 1, y - 1);
        if (actual.equals(Ocr.BLACK)) {
            result++;
        }

        actual = img.getRGB(x + 1, y);
        if (actual.equals(Ocr.BLACK)) {
            result++;
        }

        actual = img.getRGB(x + 1, y + 1);
        if (actual.equals(Ocr.BLACK)) {
            result++;
        }

        actual = img.getRGB(x, y + 1);
        if (actual.equals(Ocr.BLACK)) {
            result++;
        }

        actual = img.getRGB(x - 1, y + 1);
        if (actual.equals(Ocr.BLACK)) {
            result++;
        }

        actual = img.getRGB(x - 1, y);
        if (actual.equals(Ocr.BLACK)) {
            result++;
        }

        actual = img.getRGB(x - 1, y - 1);
        if (actual.equals(Ocr.BLACK)) {
            result++;
        }



        return result;
    }

    private Integer a(BufferedImage img, Integer x, Integer y) {
        Integer result = 0;
        Integer actual;
        String transiciones = "";

        actual = img.getRGB(x, y - 1);

        if (actual.equals(Ocr.BLACK)) {
            transiciones += "1";
        } else {
            transiciones += "0";
        }

        actual = img.getRGB(x + 1, y - 1);
        if (actual.equals(Ocr.BLACK)) {
            transiciones += "1";
        } else {
            transiciones += "0";
        }


        actual = img.getRGB(x + 1, y);
        if (actual.equals(Ocr.BLACK)) {
            transiciones += "1";
        } else {
            transiciones += "0";
        }


        actual = img.getRGB(x + 1, y + 1);
        if (actual.equals(Ocr.BLACK)) {
            transiciones += "1";
        } else {
            transiciones += "0";
        }

        actual = img.getRGB(x, y + 1);
        if (actual.equals(Ocr.BLACK)) {
            transiciones += "1";
        } else {
            transiciones += "0";
        }

        actual = img.getRGB(x - 1, y + 1);
        if (actual.equals(Ocr.BLACK)) {
            transiciones += "1";
        } else {
            transiciones += "0";
        }

        actual = img.getRGB(x - 1, y);
        if (actual.equals(Ocr.BLACK)) {
            transiciones += "1";
        } else {
            transiciones += "0";
        }

        actual = img.getRGB(x - 1, y - 1);
        if (actual.equals(Ocr.BLACK)) {
            transiciones += "1";
        } else {
            transiciones += "0";
        }

        actual = img.getRGB(x, y - 1);
        if (actual.equals(Ocr.BLACK)) {
            transiciones += "1";
        } else {
            transiciones += "0";
        }

        result = transiciones.split("01").length - 1;

        return result;
    }
}
